package play.go.rule;

import static play.go.rule.Color.*;

/**
 * Board of game.
 * @author shuji.w6e
 * @since 0.1.0
 */
public class Board {
    // reference
    final int[][] grid;
    final int len;
    final int size;
    final boolean[] clearChecked;
    boolean[] checked;

    static int[][] createGrid(int size) {
        int len = size + 2;
        int[][] g = new int[len][len];
        for (int i = 0; i < len; i++) {
            g[0][i] = OUT_OF_BOARD;
            g[len - 1][i] = OUT_OF_BOARD;
            g[i][0] = OUT_OF_BOARD;
            g[i][len - 1] = OUT_OF_BOARD;
        }
        return g;
    }

    public Board() {
        this(19);
    }

    public Board(int size) {
        this(createGrid(size));
    }

    /**
     * Constructor to set target grid.
     * <p>don't copy grid object.</p>
     * @param grid reference of grid
     */
    public Board(int[][] grid) {
        this.grid = grid;
        this.len = grid.length;
        this.size = len - 1;
        this.clearChecked = new boolean[grid.length * grid.length];
        clearChecked();
    }

    void clearChecked() {
        this.checked = clearChecked.clone();
    }

    public int play(int color, int x, int y) {
        resetEyes();
        assert isValid(color, x, y);
        boolean koCheck = false;
        if (grid[y][x - 1] != color && grid[y - 1][x] != color && grid[y][x + 1] != color && grid[y + 1][x] != color) {
            koCheck = true;
        }
        grid[y][x] = color;
        int opponent = opponent(color);
        int hamaL = remove(opponent, x - 1, y);
        int hamaT = remove(opponent, x, y - 1);
        int hamaR = remove(opponent, x + 1, y);
        int hamaB = remove(opponent, x, y + 1);
        int hama = hamaL + hamaT + hamaR + hamaB;
        if (hama == 1 && koCheck) { // ko?
            if (hamaL == 1) {
                assert grid[y][x - 1] == SPACE;
                grid[y][x - 1] = PROTECT;
            } else if (hamaT == 1) {
                assert grid[y - 1][x] == SPACE;
                grid[y - 1][x] = PROTECT;
            } else if (hamaR == 1) {
                assert grid[y][x + 1] == SPACE;
                grid[y][x + 1] = PROTECT;
            } else {
                assert hamaB == 1;
                assert grid[y + 1][x] == SPACE;
                grid[y + 1][x] = PROTECT;
            }
        }
        setEyes(opponent(color));
        return hama;
    }

    void resetEyes() {
        for (int x = 1; x <= size; x++) {
            for (int y = 1; y <= size; y++) {
                if (grid[y][x] == PROTECT) grid[y][x] = SPACE;
            }
        }
    }

    void setEyes(int color) {
        for (Eye eye : Eye.gets(grid)) {
            if (isSuicide(color, eye.x, eye.y)) {
                grid[eye.y][eye.x] = PROTECT;
            }
        }
    }

    boolean isValid(int color, int x, int y) {
        if (grid[y][x] != SPACE) return false;
        // TODO kou
        if (isSuicide(color, x, y)) return false;
        return true;
    }

    boolean isSuicide(int color, int x, int y) {
        assert grid[y][x] == SPACE;
        try {
            // if put ?
            grid[y][x] = color;
            clearChecked();
            if (!isSurrounded(color, x, y)) return false;
            // sorrounded suicide check 
            // you can get opponent stone or not
            int opponent = opponent(color);
            if (grid[y][x - 1] == opponent) {
                clearChecked();
                if (isSurrounded(opponent, x - 1, y)) return false;
            }
            if (grid[y - 1][x] == opponent) {
                clearChecked();
                if (isSurrounded(opponent, x, y - 1)) return false;
            }
            if (grid[y][x + 1] == opponent) {
                clearChecked();
                if (isSurrounded(opponent, x + 1, y)) return false;
            }
            if (grid[y + 1][x] == opponent) {
                clearChecked();
                if (isSurrounded(opponent, x, y + 1)) return false;
            }
            return true;
        } finally {
            // reset
            grid[y][x] = 0;
        }
    }

    boolean isSurrounded(int color, int x, int y) {
        int idx = y * len + x;
        if (checked[idx]) return true; // already checked
        checked[idx] = true;
        if (grid[y][x] == SPACE) return false;
        if (grid[y][x] == color) { // check left -> top -> right -> bottom
            if (!isSurrounded(color, x - 1, y)) return false; // Left
            if (!isSurrounded(color, x, y - 1)) return false; // Top
            if (!isSurrounded(color, x + 1, y)) return false; // Right
            if (!isSurrounded(color, x, y + 1)) return false; // Bottom
        }
        return true;
    }

    int remove(int opponent, int x, int y) {
        if (grid[y][x] != opponent) return 0;
        clearChecked();
        if (!isSurrounded(opponent, x, y)) return 0;
        return remove(opponent, x, y, 0);
    }

    int remove(int opponent, int x, int y, int count) {
        if (grid[y][x] != opponent) return count;
        count++;
        grid[y][x] = SPACE; // remove
        count = remove(opponent, x - 1, y, count);
        count = remove(opponent, x, y - 1, count);
        count = remove(opponent, x + 1, y, count);
        count = remove(opponent, x, y + 1, count);
        return count;
    }

    public int[][] getGrid() {
        return this.grid;
    }

    public void replay(String record) {
        for (String sgf : record.split(";")) {
            if (sgf.length() == 0) continue;
            int[] p = SmartGameFormat.parse(sgf);
            play(p[0], p[1], p[2]);
        }
    }

}
